/*
 * Created by Jaren at 2023/3/9 16:23
 */
import 'dart:async';
import 'dart:io';

import 'package:inmotion_protocol/base/protocol.dart';
import 'package:inmotion_protocol/common/event.dart';
import 'package:inmotion_protocol/common/exceptions.dart';
import 'package:inmotion_protocol/common/vehicle_info.dart';
import 'package:inmotion_protocol/util/byte_util.dart';
import 'package:inmotion_protocol/util/log.dart';

abstract class AbsBmsLogManager {
  Protocol protocol;

  LogConfig? logConfig;

  AbsBmsLogManager(this.protocol);

  AbsSingleBmsLogManager? singleBmsLogManager;

  bool _canceled = false;

  ProductProperty productProperty() {
    return protocol.productProperty;
  }

  void config(File dir) {
    logConfig = LogConfig();
    List<int> devices = bmsDevices();
    if (devices.isEmpty) throw InvalidParamException("BMS devices of current product model haven't been configurated.");
    for (var i = 0; i < devices.length; i++) {
      var file = File(dir.path + "/${devices[i]}.bin");
      if (file.existsSync()) file.deleteSync();
      if (!file.parent.existsSync()) file.parent.createSync(recursive: true);

      logConfig!.add(SingleLogConfig(File(dir.path + "/${devices[i]}.bin"), devices[i]));
    }
  }

  List<int> bmsDevices() {
    return [];
  }

  Stream<UploadEvent> start() {
    if (logConfig == null) {
      throw InvalidParamException("Please call config() first!");
    }
    _canceled = false;
    StreamController<UploadEvent> streamController = StreamController();

    singleBmsLogManager = createSingleBmsLogManager();

    start0(index) {
      LogUtils.d("start to request #$index bms log");
      SingleLogConfig singleConfig = logConfig!.configs[index];
      singleBmsLogManager!.setup(SingleLogConfig(singleConfig.output, singleConfig.device));
      singleBmsLogManager!.start().listen((event) {
        streamController.add(event);

        if (event is SingleFinishedEvent) {
          // 当前设备获取结束
          if (_canceled) throw UploadError(singleConfig.device, index, UploadError.errorCanceled);
          int nextIndex = index + 1;
          var hasNext = nextIndex < logConfig!.configs.length;
          if (hasNext) {
            start0(index + 1);
          } else {
            // 全部结束
            streamController.add(_genFinishedEvent());
            streamController.close();
          }
        }
      }).onError((e) {
        streamController.addError(e);
        streamController.close();
      });
    }

    start0(0);
    return streamController.stream;
  }

  void cancel() {
    _canceled = true;
    if (singleBmsLogManager != null) singleBmsLogManager!.canceled = true;
  }

  FinishedEvent _genFinishedEvent() {
    var finishedEvent = FinishedEvent(0, 0, 0);
    for (var i = 0; i < logConfig!.configs.length; i++) {
      finishedEvent.putLogFile(logConfig!.configs[i].device, logConfig!.configs[i].output);
    }
    return finishedEvent;
  }

  AbsSingleBmsLogManager createSingleBmsLogManager();
}

abstract class AbsSingleBmsLogManager {
  Protocol protocol;

  SingleLogConfig? config;

  bool canceled = false;

  AbsSingleBmsLogManager(this.protocol);

  StreamController<UploadEvent>? streamController;

  Stream<UploadEvent> start() {
    if (streamController == null || (streamController != null && streamController!.isClosed)) {
      streamController = StreamController();
    }
    canceled = false;
    startImpl(streamController!);
    return streamController!.stream;
  }

  AbsSingleBmsLogManager setup(SingleLogConfig config) {
    this.config = config;
    return this;
  }

  void startImpl(StreamController<UploadEvent> streamController);
}

class LogConfig {
  List<SingleLogConfig> configs = [];

  LogConfig add(SingleLogConfig config) {
    configs.add(config);
    return this;
  }

  SingleLogConfig? getSingle(int index) {
    if (configs.length > index) {
      return configs[index];
    }
    return null;
  }
}

class SingleLogConfig {
  File output;
  int device;

  SingleLogConfig(this.output, this.device);
}

class UploadEvent {
  int device;
  int index;
  int total;

  UploadEvent(this.device, this.index, this.total);
}

class UploadError extends InMotionProtocolException {
  int device;
  int index;

  int error;

  static const errorTimeout = 0xFF;
  static const errorParseFailed = 0xFE;
  static const errorUnknown = 0xFD;
  static const errorCanceled = 0xFC;

  UploadError(this.device, this.index, this.error);

  bool isCanceled() {
    return error == errorCanceled;
  }

  String formatErrorCode([String split = ', ']) {
    return (ByteUtil.byte2Hex(device) + ByteUtil.bytes2HexString(ByteUtil.num2BytesLe(index, 2)) + ByteUtil.byte2Hex(error)).toUpperCase();
  }
}

class SingleFinishedEvent extends UploadEvent {
  File logFile;

  SingleFinishedEvent(super.device, super.index, super.total, this.logFile);
}

class FinishedEvent extends UploadEvent with LogFinishedEvent {
  FinishedEvent(super.device, super.index, super.total);

  static FinishedEvent fromUploadEvent(UploadEvent event) {
    return FinishedEvent(event.device, 0, 0);
  }
}
